﻿namespace SenenFernandez.SmartExtensionMethods.Interactivity
{
    using System;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Threading;

    /// <summary>
    /// This class provides basic support for implementing the <see cref="INotifyPropertyChanged"/> interface and for
    /// marshalling execution to the UI thread.
    /// </summary>
    [Serializable]
    public abstract class NotificationObject : INotifyPropertyChanged, ISelectable, IDisposable
    {
        private bool isSelected;
        private DispatcherOperation operation;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets IsSelected.
        /// </summary>
        public bool IsSelected
        {
            get { return isSelected; }
            set { SetValue(() => IsSelected, ref isSelected, value); }
        }

        /// <summary>
        /// Destroy the object.
        /// </summary>
        public virtual void Dispose()
        {
            if (operation != null && operation.Status != DispatcherOperationStatus.Completed)
            {
                operation.Abort();
            }
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        public bool SetValue<T, V>(Expression<Func<T>> propertyExpression, ref V oldValue, V newValue)
        {
            bool result = false;
            if (oldValue.AreDifferent(newValue))
            {
                oldValue = newValue;
                RaisePropertyChanged(propertyExpression);
                result = true;
            }
            return result;
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        public bool SetValue<T, V>(Expression<Func<T>> propertyExpression, V value)
        {
            bool result = false;
            var type = this.GetType();
            var property = type.GetProperty(propertyExpression.GetPropertyName());
            if (property != null)
            {
                var oldValue = property.GetValue(this, null);
                if (oldValue.AreDifferent(value))
                {
                    property.SetValue(this, value, null);
                    RaisePropertyChanged(propertyExpression);
                    result = true;
                }
            }
            return result;
        }

        /// <summary>
        /// Raises the PropertyChanged event if needed.
        /// </summary>
        /// <param name="propertyName">The name of the property that changed.</param>
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null && !string.IsNullOrEmpty(propertyName))
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Raises this object's PropertyChanged event for each of the properties.
        /// </summary>
        protected virtual void RaisePropertyChanged(params string[] propertyNames)
        {
            foreach (var name in propertyNames)
            {
                RaisePropertyChanged(name);
            }
        }

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            RaisePropertyChanged(propertyExpression.GetPropertyName());
        }

        /// <summary>
        /// Invokes asynchronous actions.
        /// </summary>
        protected virtual void InvokeAsync(Action action)
        {
            if (Application.Current != null)
            {
                operation = Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
            }
        }

        /// <summary>
        /// Invokes synchronous actions.
        /// </summary>
        protected virtual void Invoke(Action action)
        {
            if (Application.Current != null)
            {
                operation = Application.Current.Dispatcher.BeginInvoke(action);
            }
        }

        /// <summary>
        /// Invokes asynchronous actions.
        /// </summary>
        protected virtual void NewTask(Action action)
        {
            Task.Factory.StartNew(action);
        }
    }
}